	dappr.extend({

		/*
		 * Dialog - uses OAuthClient to control the display of non-modal Dappr dialog
		 */ 
		dialog: (function(){
	
			var initialised = false; // Changed to 'true' once the authorised features have been returned
	
			var requestedFeatures = []; // Requested Feature Stack (awaiting approval in OAuth process by user) [ {feature:feature1, callbacks:[callback1,callback2,etc]}, {feature:feature2, callbacks:[callback3,callback4,callback5,etc]} ]
			var authorizedFeatures = []; // Authorised Feature Stack (approved in OAuth process by user) [feature1,feature2,etc]
			var rejectedFeatures = [];
			
			var ADD_NOTE = 'This web site is requesting access to your Dappr account.';
			var REVOKE_NOTE = 'Revoke permission from this web site to access your Dappr account.';
			var UPDATE_NOTE = 'This web site is requesting more access to your Dappr account.';
			
			var authoriseURL = dapprProxyURL + '?action=authorize';
			
			var dapprDialogId = '_dapprDialog';
			var dapprContextId = '_dapperContext';
			var dapprDialogContentId = '_dapprDialogContent';
			var dapprDialogContentURL = '_dapprDialogContentURL'
			var dapprContent = document.createElement('span'); // actual div element containing notification content
			
			dapprContent.setAttribute('id', dapprDialogContentId);
			
			/*
			 * Add a W3C DAP feature to the feature stack
			 */
			function addFeature$(feature, successCallback, errorCallback, r_id) {

				// check that feature is not already rejected
				// if it is then fire the ERROR callback straight away!
				if (rejectedFeatures.length > 0) {
					for (var i = 0; i < rejectedFeatures.length; i++) 
						if (rejectedFeatures[i] == feature) {
							if (dappr.isFunction(errorCallback)) {
								errorCallback.call(errorCallback, r_id);
								update();
							}
							return;
						}
				}

				// check that feature is not already authorised
				// if it is then fire the SUCCESS callback straight away!
				if (authorizedFeatures.length > 0) {
					for (var i = 0; i < authorizedFeatures.length; i++) 
						if (authorizedFeatures[i] == feature) {
							successCallback.call(successCallback, r_id);
							update();
							return;
						}
				}
				
				// otherwise, add requested feature to the queue
				if (requestedFeatures.length > 0) {
					for (var j = 0; j < requestedFeatures.length; j++) {
						if (requestedFeatures[j].feature == feature) {
							// just push the callbacks to the object
							requestedFeatures[j].callbacks.push({
								successCB: successCallback,
								errorCB: errorCallback,
								id: r_id
							});
							return;
						}
					}
				}

				// by default, add the requested feature to the requestedFeatures stack
				requestedFeatures.push({
					feature: feature,
					callbacks: [{
						successCB: successCallback,
						errorCB: errorCallback,
						id: r_id
					}]
				});
				
				// (re-)render as appropriate
				update();				
			}
			
			/*
			 * Remove a W3C DAP feature from the feature stack (success and error callbacks provided MUST match for removal to take place)
			 */
			function removeFeature$(feature, id) {
				var itemRemoved = false;
				
				// remove the feature if it exists in requestedFeatures
				for (var i = 0; i < requestedFeatures.length; i++) {
					if (feature == requestedFeatures[i].feature) {
						if (requestedFeatures[i].callbacks.length <= 0) {
							dappr.arrayRemove(requestedFeatures, i);
							i--; // move pointer back one space to account for removal of array item on previous line
							itemRemoved = true;
						}
						else {
							// match up the provided success and error callbacks
							for (var j = 0; j < requestFeatures[i].callbacks.length; j++) {
								if (id == requestedFeatures[i].callbacks[j].id) {
									dappr.arrayRemove(requestedFeatures[i].callbacks, j);
									j--; // move pointer back one space to account for removal of array item on previous line
								}
							}
						}
					}
				}
				
				// (re-)render dappr dialog as appropriate
				if(itemRemoved) update();				
			}


			/*
			 * Overwrite the class global rejectedFeatures array
			 * and fire all matching request features callbacks
			 */
			function processRejectedFeatures$(rejectedFeaturesStr) {
				// process unauthorized features
				if(dappr.isString(rejectedFeaturesStr)) {
					rejectedFeatures = rejectedFeaturesStr.split(',');
				} else {
					rejectedFeatures = []; // reset on empty
				}
				demoteRequestedFeatures();
				
				// re-render the dappr dialog display
				update();	
			}

			/*
			 * Moves requested features to rejected features
			 */
			function demoteRequestedFeatures() {
				if(!rejectedFeatures || rejectedFeatures.length<=0) return;
				for(var i=0; i<rejectedFeatures.length; i++) demoteFeature(rejectedFeatures[i]);
			}

			/*
			 * Moves and fires a feature from requestedFeatures to unauthorizedFeatures 
			 */
			function demoteFeature(feature) {
				for(var i=0; i<requestedFeatures.length; i++) {
					if(requestedFeatures[i].feature == feature) {
						// fire unauthorised feature callbacks
						fireRejectedFeature(requestedFeatures[i]); // after this point the callbacks can be discarded as fired.
						
						// remove requested from requestedFeatures
						dappr.arrayRemove(requestedFeatures, i);
						// decrement counter to account for removal of feature
						i--;
					}					
				}
			}

			/*
			 *  Trigger all registered rejectedFeature callbacks
			 */
			function fireRejectedFeature(featureObj) {					
				for (var i=0; i<featureObj.callbacks.length; i++) {
					// fire the callback provided
					if(dappr.isFunction(featureObj.callbacks[i].errorCB))
						featureObj.callbacks[i].errorCB.call(featureObj.callbacks[i].errorCB, featureObj.callbacks[i].id );					
				}
			}		
			
			
						
			
			/*
			 * Overwrite the class global authorizedFeatures array
			 * and fire all matching request features callbacks
			 */
			function processAuthorizedFeatures$(authorizedFeaturesStr) {
				// process authorized features
				if(dappr.isString(authorizedFeaturesStr)) {
					authorizedFeatures = authorizedFeaturesStr.split(',');
				} else {
					authorizedFeatures = []; // reset on empty
				}
				promoteRequestedFeatures();	
				
				// re-render the dappr dialog display
				update();	
			}
						
			/*
			 * Moves requested features to authorised features
			 */
			function promoteRequestedFeatures() {
				if(!authorizedFeatures || authorizedFeatures.length<=0) return;
				for(var i=0; i<authorizedFeatures.length; i++) promoteFeature(authorizedFeatures[i]);
			}
			
			/*
			 * Moves and fires a feature from requestedFeatures to authorizedFeatures 
			 */
			function promoteFeature(feature) {
				for(var i=0; i<requestedFeatures.length; i++) {
					if(requestedFeatures[i].feature == feature) {
						// fire authorised feature callbacks
						fireAuthorisedFeature(requestedFeatures[i]); // after this point the callbacks can be discarded as fired.
												
						// remove requested from requestedFeatures
						dappr.arrayRemove(requestedFeatures, i);
						// decrement counter to account for removal of feature
						i--;
					}					
				}
			}
			
			/*
			 *  Trigger all registered authorizedFeatures callbacks
			 */
			function fireAuthorisedFeature(featureObj) {					
				for (var i=0; i<featureObj.callbacks.length; i++) {
					// fire the callback provided
					featureObj.callbacks[i].successCB.call(featureObj.callbacks[i].successCB, featureObj.callbacks[i].id );					
				}
			}



			
			
						
			/*
			 * Update the Dappr non-model dialog display div 
			 */
			function update() {
				    var content = null;
					
					if (requestedFeatures.length > 0) {
						var requestedFeaturesStr = '';
						
						// create a comma-seperated list of requestedFeatures values
						for (var i = 0; i < requestedFeatures.length; i++) {
							requestedFeaturesStr += (requestedFeaturesStr !== '' ? ',' : '') + requestedFeatures[i].feature;
						}
						
						// Register (new) features and show the dialog (on success)
						doRegisterFeatures(requestedFeaturesStr);
					}
					else {
						// update display
						show$();
					}
					
					window.console.log('requested features:', requestedFeatures);
					window.console.log('authorized features:', authorizedFeatures);
					window.console.log('rejected features:', rejectedFeatures);
					window.console.log('-----------------------------------------');
			}
			
			function doRegisterFeatures(requestedFeaturesStr) {
						dappr.oauthClient.registerFeatures(function(json) {
							
							if(!json || json == '{}') return false;
							
							/* Features registered at the server could be returned here? */
												
							// Handle the proxy response
							switch(json.code) {
								case 200:
									// add the OAuth authorisation URL to the dapprContent container
									dappr.dialog.show();
									break;
								case 10001: // retry in 500ms
									setTimeout(function(){doRegisterFeatures(requestedFeaturesStr)}, 500);
									break;
								default: // All other error codes can be ignored
									break;
							}

						},
						null, // no error callback
						{
							requestedFeatures: requestedFeaturesStr
						});
			}
			
			function getAuthoriseDisplay() {
				var dapprAuthURL = null;

					if (authoriseURL) {	
						// DISPLAY LINK:
						//dapprAuthURL = document.createElement('a');
						//var dapprAuthURLLink = document.createAttribute('href');
						//dapprAuthURLLink.value = authoriseURL;
						//dapprAuthURL.setAttributeNode(dapprAuthURLLink);
						//dapprAuthURL.setAttribute('id',dapprDialogContentURL);
						//dapprAuthURL.setAttribute('style','color:blue;padding-left:10px;');
						//dapprAuthURL.innerHTML = 'Click for more info';
						
						// DISPLAY BUTTON
						dapprAuthURL = document.createElement('input');
						dapprAuthURL.type = 'button';
						dapprAuthURL.value = 'Show Settings...';
						dapprAuthURL.setAttribute('style', 'width:150px;height:24px;margin-left:30px;');

						// add 'on click' event handler to invoke OAuth Popup window as required
						dapprAuthURL.onclick = function(evt) {
							evt.preventDefault();
							
							// open popup window with OAuth Authorisation URL
							dappr.popup.open(authoriseURL, 640, 480);
						};
					}	
					
				return dapprAuthURL;
			}
			
			/*
			 * Display the Dappr non-modal dialog control in UI
			 */
			function show$() {
				var note = (authorizedFeatures.length>0 ? 
					((requestedFeatures.length>0) ? 
						UPDATE_NOTE 
							: REVOKE_NOTE) 
								: ADD_NOTE);
				
				if (requestedFeatures.length > 0 || authorizedFeatures.length > 0 || rejectedFeatures.length > 0) {
						var authURLDisplay = getAuthoriseDisplay();
						if(!authURLDisplay) { hide(); return; }
					
						var dapprDisplay = document.createElement('div');
						dapprDisplay.setAttribute('id', dapprDialogId);
						
						dapprDisplay.setAttribute('style', 'position:fixed;top:0px;left:0px;width:100%;background-color:#ACC4EA;' + 
														   'color:#000000;margin:0px;padding:10px 10px 5px 10px;height:26px;' + 
														   'border-bottom:1px solid #999;font-weight:normal;text-align:left;' + 
														   'font-family:Arial,Helvetica,sans-serif;font-size:18px;opacity:0.95;');
						
						// add note to new context
						var newContext = document.createElement('span');
						newContext.setAttribute('id', dapprContextId);
						newContext.innerHTML = note;
						dapprDisplay.appendChild(newContext);
						
						// append the authorisation url to the content
						dapprContent.innerHTML = '';
						dapprContent.appendChild(authURLDisplay);
						
						// append authorisation link
						dapprDisplay.appendChild(dapprContent);
						
						// remove any existing element
						hide();
						
						// add new element
						pinToBody(dapprDisplay, true);
				} else {
					hide();
				}
			}
			
			function pinToBody(element, addGhost) {
				var body = document.getElementsByTagName("body")[0] || document.documentElement;
				
				body.insertBefore(element, body.firstChild);
				
				// Create a 'ghost element' to produce necessary padding at top of page...
				if (addGhost) {
					var ghost = document.createElement('div');
					
					var ghostHeight = 42;
					
					ghost.setAttribute('id', dapprDialogId + '_ghost');
					ghost.setAttribute('style', 'display:block;position:relative;width:1px;clear:both;' +
												'margin:0px;padding:0px;height:' + ghostHeight + 'px;');
												
					body.insertBefore(ghost, body.firstChild);
				}
			}
			
			/*
			 * Remove the Dappr non-modal dialog control from UI
			 */
			function hide() {
				// Remove dialog:
				var dapprDisplayOld = document.getElementById(dapprDialogId);				
				if(dapprDisplayOld) {
					dapprDisplayOld.parentNode.removeChild(dapprDisplayOld);
					// Remove dialog 'ghost':
					var dapprDisplayGhostOld = document.getElementById(dapprDialogId + '_ghost');
					(dapprDisplayGhostOld ? dapprDisplayGhostOld.parentNode.removeChild(dapprDisplayGhostOld) : false);
				}
			}


			
			return {
				
				addFeature: function(feature, callback, errorCallback, id) {
					return addFeature$(feature, callback, errorCallback, id);
				},
				
				removeFeature: function(feature, id) {
					return removeFeature$(feature, id);
				},
				
				setAuthorizedFeatures: function(authorized) {
					processAuthorizedFeatures$(authorized);
				},
				
				setRejectedFeatures: function(rejected) {
					processRejectedFeatures$(rejected);
				},


				// used from authorisation url callback
				show: function() {
					return show$();
				}
			};
		}())
		
	});
